/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.maven.nodetype;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.rmi.ServerException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import offset.nodes.Constants;
import offset.nodes.maven.RepositoryMojo;
import offset.nodes.maven.nodetype.writer.DAOWriter;
import offset.nodes.maven.nodetype.writer.InterfaceWriter;
import offset.nodes.maven.nodetype.JavaPrinter;
import offset.nodes.maven.nodetype.Name;
import offset.nodes.maven.nodetype.vo.PropertyDefVO;
import offset.nodes.maven.nodetype.vo.ChildDefVO;
import offset.nodes.maven.nodetype.vo.NodeTypeVO;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

/**
 * Plugin to create DAOs for nodes.
 * 
 * @goal generate-nodetypes
 * @phase generate-sources
 * 
 * @author Walter L�tgenau
 */
public class NodeTypeMojo extends RepositoryMojo {

    public static final String GENERATED_PACKAGE = "offset.nodes.jcr";
    public static final String INTERFACE_SUFFIX = ".java";
    public static final String DAO_SUFFIX = "DAO.java";

    /**
     * The location of the generated bundle files
     * @parameter expression="${project.build.directory}"
     * @required
     */
    private File outputDirectory;
    /**
     * The project
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;
    /**
     * The user name in the repository
     * @parameter expression="${nodes.repository.userName}"
     * @required
     */
    private String userName;
    /**
     * The password of the user
     * @parameter expression="${nodes.repository.password}"
     * @required
     */
    private String password;
    /**
     * The RMI URL of the repository
     * @parameter expression="${nodes.repository.rmiUrl}"
     */
    private String rmiUrl;
    Session session;

    public NodeTypeMojo() {
    }

    /**
     * Read the information from the repository. Update it from the resources, e.g. add new bundle entries.
     * Reread the now complete information, generate the bundles.
     * 
     * @throws MojoExecutionException
     */
    public void execute()
            throws MojoExecutionException {
        try {
            session = initSession(userName, password, rmiUrl);

            // For all nodes in the node type directory call createNodeTypeJava
            Node root = session.getRootNode();
            Node nodeTypes = root.getNode(Constants.CONFIG_NODETYPES.substring(1));
            String sourceDir = this.outputDirectory.getAbsolutePath() + Constants.PATH_SEPARATOR + GENERATED_SOURCES;

            HashMap<String, NodeTypeVO> types = new HashMap<String, NodeTypeVO>();
            NodeIterator ni = nodeTypes.getNodes();
            while (ni.hasNext()) {
                Node node = ni.nextNode();

                NodeTypeVO nodeType = createNodeTypeVO(node);
                types.put(nodeType.getName(), nodeType);
            }

            for (NodeTypeVO nodeType : types.values()) {
                getLog().info("Generating " + nodeType.getName());
                createNodeTypeJava(nodeType, sourceDir, types);
            }

            getLog().info("Generated " + types.size() + " classes");

        } catch (ServerException se) {
            getLog().info("Could not connect to the JCR repository");
        } catch (Exception ex) {
            throw new MojoExecutionException(ex.getMessage(), ex);
        }
    }

    protected NodeTypeVO createNodeTypeVO(Node nodeType) throws RepositoryException {
        NodeTypeVO result = new NodeTypeVO();

        result.setName(nodeType.getName());
        Value[] superTypes = nodeType.getProperty(Constants.PROP_SUPERTYPES).getValues();
        String[] types = new String[superTypes.length];
        for (int i = 0; i < types.length; i++) {
            types[i] = superTypes[i].getString();
        }

        result.setSuperTypes(types);

        NodeIterator properties = nodeType.getNodes(Constants.CHILD_PROPERTY_DEFINITION);
        while (properties.hasNext()) {
            result.getProperties().add(createPropertyDefVO(properties.nextNode()));
        }

        NodeIterator children = nodeType.getNodes(Constants.CHILD_CHILDNODE_DEFINITION);
        while (children.hasNext()) {
            result.getChildren().add(createChildDefVO(children.nextNode()));
        }

        return result;
    }

    protected PropertyDefVO createPropertyDefVO(Node propertyDefinition) throws RepositoryException {
        PropertyDefVO result = new PropertyDefVO();

        if (propertyDefinition.hasProperty(Constants.PROP_NAME))
            result.setName(propertyDefinition.getProperty(Constants.PROP_NAME).getString());
        result.setType(propertyDefinition.getProperty(Constants.PROP_REQUIRED_TYPE).getString());
        result.setMultiple(propertyDefinition.getProperty(Constants.PROP_MULTIPLE).getBoolean());

        return result;
    }

    protected ChildDefVO createChildDefVO(Node childNodeDefinition) throws RepositoryException {
        ChildDefVO result = new ChildDefVO();

        if (childNodeDefinition.hasProperty(Constants.PROP_NAME))
            result.setName(childNodeDefinition.getProperty(Constants.PROP_NAME).getString());
        String typeName = null;
        if (childNodeDefinition.hasProperty(Constants.PROP_DEFAULT_PRIMARY_TYPE))
            typeName = childNodeDefinition.getProperty(Constants.PROP_DEFAULT_PRIMARY_TYPE).getString();
        else
            typeName = childNodeDefinition.getProperty(Constants.PROP_REQUIRED_PRIMARY_TYPES).getValues()[0].getString();
        result.setPrimaryType(typeName);
        result.setMultiple(childNodeDefinition.getProperty(Constants.PROP_SAME_NAME_SIBLINGS).getBoolean());

        return result;
    }

    protected void createNodeTypeJava(NodeTypeVO nodeType, String sourceDir, HashMap<String, NodeTypeVO> types) throws RepositoryException, IOException {
        // create files from name
        Name name = new Name(nodeType.getName());

        // interface
        String packagePath = GENERATED_PACKAGE.replaceAll("\\.", Constants.PATH_SEPARATOR) + Constants.PATH_SEPARATOR + name.getPrefix();
        File interfaceFile = new File(sourceDir + Constants.PATH_SEPARATOR + packagePath + Constants.PATH_SEPARATOR + name.nameToJavaClassName() + INTERFACE_SUFFIX);
        interfaceFile.getParentFile().mkdirs();
        if (!interfaceFile.exists())
            interfaceFile.createNewFile();
        JavaPrinter interfacePrinter = new JavaPrinter(new BufferedOutputStream(new FileOutputStream(interfaceFile)));

        InterfaceWriter interfaceWriter = new InterfaceWriter(nodeType, interfacePrinter, types);
        interfaceWriter.write();

        // DAO
        File daoFile = new File(interfaceFile.getParent() + Constants.PATH_SEPARATOR + name.nameToJavaClassName() + DAO_SUFFIX);
        if (!daoFile.exists())
            daoFile.createNewFile();
        JavaPrinter printer = new JavaPrinter(new BufferedOutputStream(new FileOutputStream(daoFile)));

        DAOWriter daoWriter = new DAOWriter(nodeType, printer, types);
        daoWriter.write();
    }
}
